热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

权衡|后记_利用xcodeproj给主工程添加子工程

篇首语:本文由编程笔记#小编为大家整理,主要介绍了利用xcodeproj给主工程添加子工程相关的知识,希望对你有一定的参考价值。

篇首语:本文由编程笔记#小编为大家整理,主要介绍了利用xcodeproj给主工程添加子工程相关的知识,希望对你有一定的参考价值。




现在,ccocoapods已经成为ios工程的标配,在这个工具的开发过程中,开源了一个专门用来操作工程的.xcodeproj文件的ruby库Xcodeproj,利用它,我们自己也可以用ruby脚本来添加和删除工程中的文件等,做到自动化操作



问题的提出

在我们的组件化过程中,是通过子工程的方式来建立业务组件的.可能有人会问,为什么不用pod来建立业务组件呢?其实当时也有考虑过,pod更适合已经比较成熟的组件,而我们现在的业务变动还很大,并且pod在开发的过程中,新增文件什么的,还要运行下pod install才能运行,综合考虑,在业务早期,还是使用子工程的方式更便捷,能取得各方面的权衡

当我们采用子工程来建立业务组件,那么通常建立了一个模板化的组件工程(可以通过多种方式建立,此处不述了)后,还要做4件事,才能添加到主工程中,如下图所示:


  1. 拖动工程到主工程中
  2. 设置Target Dependencies,因为每个组件工程有个资源bundle的target,如果不设置依赖,当他们改动时候,主工程并不会去编译它们.
  3. 设置Link Binary With Libraries
  4. 拷贝资源

虽然事情不到,总归还是觉得建立组件和将组件添加到工程,是割裂的,难免有遗憾.
我们每天都使用的cocoapods,就是一个脚本就建立好了工程和设置完成了依赖,于是就想着用ruby借助xcodeproj库来将建立工程和设置结合起来.


xcodeproj介绍

Xcodeproj是cocoapods团队在写cocoapods过程中开源出来的库,它的工程代码赏心悦目,结构化程度很高.并且还提供了很多单元测试.不过遗憾的是,它没有个详细的使用文档,加上使用了在国内比较小众的ruby语言编写的,所以使用起来,还是颇费一番周折的.

网上有不少中文的使用教程,它们都是简单的添加.h或者.m文件等等,相对来说比较简单.对于怎么给工程添加子工程,倒是没有人叙述过.无奈只能自己各种尝试,还是不得要领,又想到,cocoapods怎么是怎么做到的呢?
于是为了解决我的这个问题,我将cocoapods源码也下载下来进行阅读分析,其实最理想还是能调试就好了,但无奈,对ruby的熟悉度有限,再加上这个工程着实庞大,还是没办法.不过在阅读源码的过程中还是有很大的收获的.


各种尝试

从cocoapods源码的阅读过程中,发现了xcodeproj库竟然有个file_references_factory.rb文件,在其中

def new_reference(group, path, source_tree)
ref = case File.extname(path).downcase
when '.xcdatamodeld'
new_xcdatamodeld(group, path, source_tree)
when '.xcodeproj'
new_subproject(group, path, source_tree)
else
new_file_reference(group, path, source_tree)
end
configure_defaults_for_file_reference(ref)
ref
end

然后在group.rb中

def new_reference(path, source_tree = :group)
FileReferencesFactory.new_reference(self, path, source_tree)
end

难道问题这么简单,直接就可以使用啊
赶紧写段代码试试,命名为createProjectDependcy.rb

require 'xcodeproj'
def addSubProj
projectPath = "DemoMain.xcodeproj"
project = Xcodeproj::Project.open(projectPath)
project.main_group.new_reference("Modules/YLBusiness1/YLBusiness1.xcodeproj",:group)
project.save
end
addSubProj

在终端执行

ruby ./createProjectDependcy.rb

再看看工程

成功了!!

看来问题其实很简单啊,就按普通文件的方式来添加就好了,这方法内部,已经针对是.xcodeproj处理了

不过当我们这个时候手动添加 Target Dependencies或者Link Binary的时候,xcode会crash掉!!

这还没完,这种方式添加的子工程,当我们在xcode中删除的时候,会导致工程中的products这个group中的文件消失了!!

删除前

删除后

xcodeproj竟然有这么严重的问题,一直没有人反馈过…

怎么办?

难道这个问题解决不了了??


解决方式

既然xcodeproj这段代码是有问题的,那么要解决问题,只能我们自己修改了.

首先,既然上面的代码能够添加成功,那么说明总体上应该是没啥问题的,只是代码中有些问题,至于问题出在哪,目前还不清楚

我们首先用手动拖动的形式,来给主工程添加子工程,然后将project.pbxproj文件保存下来,再通过上面代码的方式来添加,也把project.pbxproj文件保存下来,两个进行对比,看看有什么不同的地方

具体的对比过程是乏味冗长的,通过对比发现,手动拖动生成的,多了一个不在xcode工程可视化中出现的group

4C5117FE2255AD3500914224 /* Products */ =
isa = PBXGroup;
children = (
4C5118042255AD3500914224 /* libBusiness1.a */,
4C5118062255AD3500914224 /* Business1Tests.xctest */,
4C5118082255AD3500914224 /* Business1Bundle.bundle */,
);
name = Products;
sourceTree = "";
;
在projectReferences的ProductGroup中使用的是上面建立的group
projectReferences = (

ProductGroup = 4C5117FE2255AD3500914224 /* Products */;
ProjectRef = 4C5117FD2255AD3500914224 /* YLBusiness1.xcodeproj */;
,

而通过xcodeproj这段代码生成的是在原来的products这个group下添加了引用,以下 4CFBA7F42099B5BC00E39A19这个Products group是原来存在的!

4CFBA7F42099B5BC00E39A19 /* Products */ =
isa = PBXGroup;
children = (
4C51181C2255AEF600914224 /* DemoMain.app */,
4C51181D2255AEF600914224 /* DemoMainTests.xctest */,
4C51181E2255AEF600914224 /* DemoMainUITests.xctest */,
D5EEDB2A75FE09CBA854F57C /* libBusiness1.a */,
73D1A8603F14BDF13804CD30 /* Business1Tests.xctest */,
A5CD1082BFBEC674BC72C28A /* Business1Bundle.bundle */,
);
name = Products;
sourceTree = "";
;
使用的时候

ProductGroup = 4CFBA7F42099B5BC00E39A19 /* Products */;
ProjectRef = A06BA4184D5F5B2B56B8D071 /* YLBusiness1.xcodeproj */;
,

问题就是出在这里了,添加子工程,不应该重用工程原来的products group,重用了,导致删除的时候,会清掉这个group.

至于为什么添加依赖会导致xcode crash,从这里的分析看,应该也是和这个projectReferences有关.

既然知道问题所在,那么我么就可以想办法解决了
从源码中可看到,添加子工程的方法是FileReferencesFactory中的 new_subproject方法

def new_subproject(group, path, source_tree)
ref = new_file_reference(group, path, source_tree)
ref.include_in_index = nil
product_group_ref = find_products_group_ref(group, true)
subproj = Project.open(path)
subproj.products_group.files.each do |product_reference|
container_proxy = group.project.new(PBXContainerItemProxy)
container_proxy.container_portal = ref.uuid
container_proxy.proxy_type = Constants::PROXY_TYPES[:reference]
container_proxy.remote_global_id_string = product_reference.uuid
container_proxy.remote_info = 'Subproject'
reference_proxy = group.project.new(PBXReferenceProxy)
extension = File.extname(product_reference.path)[1..-1]
reference_proxy.file_type = Constants::FILE_TYPES_BY_EXTENSION[extension]
reference_proxy.path = product_reference.path
reference_proxy.remote_ref = container_proxy
reference_proxy.source_tree = 'BUILT_PRODUCTS_DIR'
product_group_ref << reference_proxy
end
attribute &#61; PBXProject.references_by_keys_attributes.find |attrb| attrb.name &#61;&#61; :project_references
project_reference &#61; ObjectDictionary.new(attribute, group.project.root_object)
project_reference[:project_ref] &#61; ref
project_reference[:product_group] &#61; product_group_ref
group.project.root_object.project_references << project_reference
ref
end

这段代码中的

product_group_ref &#61; find_products_group_ref(group, true)

是获取工程中原来的Products group,这个造成了上面的问题,所以,我们要修改它

稍微修改下此代码,当然,我们这里,因为并不是在原来代码的类上写,需要把一些类的前缀都加上

def add_new_subProj(group, path, source_tree)
ref &#61; Xcodeproj::Project::FileReferencesFactory.send(:new_file_reference, group, path, source_tree)
ref.include_in_index &#61; nil
ref.name &#61; Pathname(path).basename.to_s
#product_group_ref &#61; group.new_group("Products") 这种方式创建的group会挂载在main_group下,这会导致删除的时候,出现一个空的group,而手动拖动的就不会,所以改为group.project.new(Xcodeproj::Project::PBXGroup)
#从xcode手动添加子工程来看,它要创建一个包含子工程的group
product_group_ref &#61; group.project.new(Xcodeproj::Project::PBXGroup) #find_products_group_ref(group, true)
product_group_ref.name &#61; "Products" #手动拖动创建的group名字是Products,所以我们这里新创建的名字也赋值为products
subproj &#61; Xcodeproj::Project.open(path)
subproj.products_group.files.each do |product_reference|
container_proxy &#61; group.project.new(Xcodeproj::Project::PBXContainerItemProxy)
container_proxy.container_portal &#61; ref.uuid
container_proxy.proxy_type &#61; Xcodeproj::Constants::PROXY_TYPES[:reference]
container_proxy.remote_global_id_string &#61; product_reference.uuid
container_proxy.remote_info &#61; &#39;Subproject&#39;
reference_proxy &#61; group.project.new(Xcodeproj::Project::PBXReferenceProxy)
extension &#61; File.extname(product_reference.path)[1..-1]
reference_proxy.file_type &#61; Xcodeproj::Constants::FILE_TYPES_BY_EXTENSION[extension]
reference_proxy.path &#61; product_reference.path
reference_proxy.remote_ref &#61; container_proxy
reference_proxy.source_tree &#61; &#39;BUILT_PRODUCTS_DIR&#39;
product_group_ref << reference_proxy
end
attribute &#61; Xcodeproj::Project::PBXProject.references_by_keys_attributes.find |attrb| attrb.name &#61;&#61; :project_references
project_reference &#61; Xcodeproj::Project::ObjectDictionary.new(attribute, group.project.root_object)
project_reference[:project_ref] &#61; ref
project_reference[:product_group] &#61; product_group_ref
group.project.root_object.project_references << project_reference
ref

end
def addSubProjTest
projectPath &#61; "DemoMain.xcodeproj"
project &#61; Xcodeproj::Project.open(projectPath)
add_new_subProj(project.main_group,"Modules/YLBusiness1/YLBusiness1.xcodeproj",:group)
project.save
end
addSubProjTest

测试一下,完成了!!
删除,添加dependcy等操作都完全可以了!!

到这里,其实已经可以添加了
当然了,上面的代码依然不完美,

我们看到,手动拖动进来的 ,建立的PBXContainerItemProxy对象的remote_info赋值为的是子工程的target的名字,而我们上面代码创建的是

container_proxy.remote_info &#61; &#39;Subproject&#39;

虽然不改,不会出错什么的,以后一旦在xcode中添加link等等,xcode会自动修正这个值,但,我们在建立的时候,就做到和xcode的默认行为一致会更好.

增加一个方法

#根据productReference 找到其对应的target
def get_target_with_productReference(productReference,project)
project.native_targets.each |target|
if target.product_reference &#61;&#61; productReference
puts "target &#61; #target"
return target
end

end
然后修改上面代码中的
#container_proxy.remote_info &#61; &#39;Subproject&#39;
subproj_native_target &#61; get_target_with_productReference(product_reference,subproj)
container_proxy.remote_info &#61; subproj_native_target.name

完美!


添加Link Binary With Libraries

核心是调用

native_target.frameworks_build_phase.add_file_reference(reference_proxy)

添加依赖

native_target.dependencies <

#添加资源

native_target.resources_build_phase.files << build_file

我将上面的综合起来,生成一个类

class SubProjectDispose
attr_reader :mainproj_path, :subproj_path, :main_project ,:sub_project,:subproj_ref_in_mainproj,:subproj_product_group_ref
def initialize(mainproj_path,subproj_path)
&#64;mainproj_path &#61; mainproj_path
&#64;subproj_path &#61; subproj_path
&#64;main_project &#61; Xcodeproj::Project.open(mainproj_path)
end
#根据productReference 找到其对应的target
def get_target_with_productReference(productReference,project)
project.native_targets.each |target|
if target.product_reference &#61;&#61; productReference
puts "target &#61; #target"
return target
end

end
def add_new_subProj(group, path, source_tree)
&#64;subproj_ref_in_mainproj &#61; Xcodeproj::Project::FileReferencesFactory.send(:new_file_reference, group, path, :group)
&#64;subproj_ref_in_mainproj.include_in_index &#61; nil
&#64;subproj_ref_in_mainproj.name &#61; Pathname(subproj_path).basename.to_s
#product_group_ref &#61; group.new_group("Products") 这种方式创建的group会挂载在main_group下,这会导致删除的时候,出现一个空的group,而手动拖动的就不会,所以改为group.project.new(Xcodeproj::Project::PBXGroup)
#从xcode手动添加子工程来看,它要创建一个包含子工程的group
product_group_ref &#61; group.project.new(Xcodeproj::Project::PBXGroup)
product_group_ref.name &#61; "Products" #手动拖动创建的group名字就是Products
&#64;sub_project &#61; Xcodeproj::Project.open(path) #打开子工程
&#64;sub_project.products_group.files.each do |product_reference|
puts "product_reference &#61; #product_reference,name &#61; #product_reference.name,path &#61; #product_reference.path"#product_reference &#61; FileReference,name &#61; ,path &#61; ChencheMaBundle.bundle reference_proxy.file_type &#61; wrapper.plug-in
container_proxy &#61; group.project.new(Xcodeproj::Project::PBXContainerItemProxy)
container_proxy.container_portal &#61; &#64;subproj_ref_in_mainproj.uuid
container_proxy.proxy_type &#61; Xcodeproj::Constants::PROXY_TYPES[:reference]
container_proxy.remote_global_id_string &#61; product_reference.uuid
#container_proxy.remote_info &#61; &#39;Subproject&#39; #这里和手动添加的是不一致的,手动的,这里是targets的名字
subproj_native_target &#61; get_target_with_productReference(product_reference,&#64;sub_project)
container_proxy.remote_info &#61; subproj_native_target.name
reference_proxy &#61; group.project.new(Xcodeproj::Project::PBXReferenceProxy)
extension &#61; File.extname(product_reference.path)[1..-1]
puts("product_reference.path &#61; #product_reference.path")
if extension &#61;&#61; "bundle"
#xcodeproj的定义中,后缀为bundle的对应的是&#39;bundle&#39; &#61;> &#39;wrapper.plug-in&#39;,但是我们手动拖动添加的是 &#39;wrapper.cfbundle&#39;
reference_proxy.file_type &#61; &#39;wrapper.cfbundle&#39;
elsif
reference_proxy.file_type &#61; Xcodeproj::Constants::FILE_TYPES_BY_EXTENSION[extension]
end
reference_proxy.path &#61; product_reference.path
reference_proxy.remote_ref &#61; container_proxy
reference_proxy.source_tree &#61; &#39;BUILT_PRODUCTS_DIR&#39;
product_group_ref << reference_proxy
end
&#64;subproj_product_group_ref &#61; product_group_ref
attribute &#61; Xcodeproj::Project::PBXProject.references_by_keys_attributes.find |attrb| attrb.name &#61;&#61; :project_references
project_reference &#61; Xcodeproj::Project::ObjectDictionary.new(attribute, group.project.root_object)
project_reference[:project_ref] &#61; &#64;subproj_ref_in_mainproj
project_reference[:product_group] &#61; product_group_ref
group.project.root_object.project_references << project_reference
product_group_ref
end
def add_subproject()
add_new_subProj(self.main_project.main_group,self.subproj_path,:group)
add_frameworks_build_phase()
add_dependencies()
add_copy_bundle_resource()
end
def add_frameworks_build_phase()
puts("self.subproj_product_group_ref &#61; #self.subproj_product_group_ref")
reference_proxys &#61; self.subproj_product_group_ref.children.grep(Xcodeproj::Project::PBXReferenceProxy)
reference_proxys.each do |reference_proxy|
if (reference_proxy.file_type &#61;&#61; Xcodeproj::Constants::FILE_TYPES_BY_EXTENSION["a"]) || (reference_proxy.file_type &#61;&#61; Xcodeproj::Constants::FILE_TYPES_BY_EXTENSION["bundle"]) then
puts("reference_proxy &#61; #reference_proxy")
native_target &#61; self.main_project.native_targets.first
native_target.frameworks_build_phase.add_file_reference(reference_proxy)
end
end
end
def add_dependencies()
#添加target的dependencies,需要的是子工程的target
# &#64;main_project &#61; Xcodeproj::Project.open(self.mainproj_path)
# &#64;sub_project &#61; Xcodeproj::Project.open(self.subproj_path) #打开子工程
# &#64;subproj_ref_in_mainproj &#61; &#64;main_project.objects_by_uuid[&#39;0CC9D5720EABC826EE0ECB3B&#39;] #使用uuid可以获取任何一个对象
native_target &#61; self.main_project.native_targets.first
&#64;sub_project.native_targets.each do |nativeTarget|
if (nativeTarget.product_type &#61;&#61; Xcodeproj::Constants::PRODUCT_TYPE_UTI[:static_library]) || (nativeTarget.product_type &#61;&#61; Xcodeproj::Constants::PRODUCT_TYPE_UTI[:bundle]) then
puts("nativeTarget.productType &#61; #nativeTarget.product_type")
container_proxy &#61; self.main_project.new(Xcodeproj::Project::PBXContainerItemProxy)
container_proxy.container_portal &#61; &#64;subproj_ref_in_mainproj.uuid
container_proxy.proxy_type &#61; Xcodeproj::Constants::PROXY_TYPES[:native_target] #1
container_proxy.remote_global_id_string &#61; nativeTarget.uuid
container_proxy.remote_info &#61; nativeTarget.product_name
target_dependency &#61; &#64;main_project.new(Xcodeproj::Project::PBXTargetDependency)
target_dependency.name &#61; nativeTarget.name
target_dependency.target_proxy &#61; container_proxy
native_target.dependencies << target_dependency
end
end
end
def add_copy_bundle_resource()
puts("add_copy_bundle_resource")
# &#64;main_project &#61; Xcodeproj::Project.open(self.mainproj_path)
# &#64;sub_project &#61; Xcodeproj::Project.open(self.subproj_path) #打开子工程
# &#64;subproj_product_group_ref &#61; &#64;main_project.objects_by_uuid[&#39;37A142A1F74B773563256D88&#39;] #使用uuid可以获取任何一个对象
native_target &#61; self.main_project.native_targets.first
build_file &#61; &#64;main_project.new(Xcodeproj::Project::PBXBuildFile)
reference_proxys &#61; self.subproj_product_group_ref.children.grep(Xcodeproj::Project::PBXReferenceProxy)
reference_proxys.each do |reference_proxy|

推荐阅读
author-avatar
翔念式的天空_549
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有